// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright(C) 2005-2006, Thomas Gleixner <tglx@linutronix.de>
 *  Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar
 *  Copyright(C) 2006-2007  Timesys Corp., Thomas Gleixner
 *
 *  NOHZ implementation for low and high resolution timers
 *
 *  Started by: Thomas Gleixner and Ingo Molnar
 */
#include <linux/compiler.h>
#include <linux/tick.h>
#include <linux/percpu.h>

#include "../inc/tick.h"
#include "../inc/tick-sched.h"
#include "../inc/timekeeping.h"

struct jiffies_update
{
    ktime_t last_ktime;
    unsigned long last_jiffies;
};

/*
 * Per-CPU nohz control structure
 */
static DEFINE_PER_CPU(struct tick_sched, tick_cpu_sched);

/*
 * The time when the last jiffy update happened. Write access must hold
 * jiffies_lock and jiffies_seq. tick_nohz_next_event() needs to get a
 * consistent view of jiffies and last_jiffies_update.
 */

static struct jiffies_update jiffy_update;

struct tick_sched *this_cpu_tick_sched(void)
{
    return this_cpu_ptr(&tick_cpu_sched);
}

/*
 * Read jiffies and the time when jiffies were updated last
 */
u64 get_jiffies_update(unsigned long *basej)
{
    u64 basemono;

	basemono = jiffy_update.last_ktime;
	*basej = jiffy_update.last_jiffies;

	return basemono;
}

/*
 * Must be called with interrupts disabled !
 */
void tick_do_update_jiffies64(struct tick_sched *ts, ktime_t now)
{
    ktime_t delta;
    unsigned long ticks;
    int cpu = smp_processor_id();

    if (__tick_do_timer_cpu() != cpu)
        return;

    delta = ktime_sub(now, jiffy_update.last_ktime);
    if (delta > 0)
    {
        ticks = delta / TICK_NSEC;

        jiffy_update.last_ktime += ((u64)TICK_NSEC * ticks);
        jiffy_update.last_jiffies += ticks;

        jiffies_increase(ticks);
    }
}

static void tick_sched_do_timer(struct tick_sched *ts, ktime_t now)
{
    tick_do_update_jiffies64(ts, now);
}

static void tick_sched_handle(struct tick_sched *ts)
{
    update_process_times(0);
}

/*
 * We rearm the timer until we get disabled by the idle code.
 * Called with interrupts disabled.
 */
static enum hrtimer_restart sched_timer_handler(struct hrtimer *timer)
{
    struct tick_sched *ts = container_of(timer, struct tick_sched, sched_timer);
    ktime_t now = ktime_get();

    tick_sched_do_timer(ts, now);

    tick_sched_handle(ts);

    /*
     * In dynticks mode, tick reprogram is deferred:
     * - to the idle task if in dynticks-idle
     * - to IRQ exit if in full-dynticks.
     */
    if (unlikely(tick_sched_flag_test(ts, TS_FLAG_STOPPED)))
        return HRTIMER_NORESTART;

    hrtimer_forward(timer, now, TICK_NSEC);

    return HRTIMER_RESTART;
}

/*
 * Initialize and return retrieve the jiffies update.
 */
static ktime_t tick_init_jiffy_update(void)
{
    ktime_t period;

    jiffy_update.last_ktime = ktime_get();
    jiffy_update.last_jiffies = jiffies;

    period = jiffy_update.last_ktime;

    return period;
}

/**
 * tick_setup_sched_timer - setup the tick emulation timer
 * @hrtimer: whether to use the hrtimer or not
 */
void tick_setup_sched_timer(bool hrtimer)
{
    struct tick_sched *ts = this_cpu_tick_sched();

    /* Emulate tick processing via per-CPU hrtimers: */
    hrtimer_setup(&ts->sched_timer, sched_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_HARD);
    if (IS_ENABLED(CONFIG_HIGH_RES_TIMERS) && hrtimer)
        tick_sched_flag_set(ts, TS_FLAG_HIGHRES);

    /* Get the next period (per-CPU) */
    hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());

    hrtimer_forward_now(&ts->sched_timer, TICK_NSEC);
    if (IS_ENABLED(CONFIG_HIGH_RES_TIMERS) && hrtimer)
        hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED_HARD);
    else
        tick_program_event(hrtimer_get_expires(&ts->sched_timer), 1);
    tick_nohz_activate(ts);
}

/*
 * Check if a change happened, which makes oneshot possible.
 *
 * Called cyclically from the hrtimer softirq (driven by the timer
 * softirq). 'allow_nohz' signals that we can switch into low-res NOHZ
 * mode, because high resolution timers are disabled (either compile
 * or runtime). Called with interrupts disabled.
 */
bool tick_check_oneshot_change(int allow_nohz)
{
	struct tick_sched *ts = this_cpu_tick_sched();

	if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
		return false;

	if (!allow_nohz)
		return true;

	return false;
}
